package com.crazymaker.springcloud.message.config;

import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.crazymaker.springcloud.message.core.DsShardingAlgorithm;
import com.crazymaker.springcloud.message.core.DynamicDataSourceDecorator;
import com.crazymaker.springcloud.message.core.TableShardingAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.ShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.orchestration.config.OrchestrationConfiguration;
import org.apache.shardingsphere.orchestration.reg.api.RegistryCenterConfiguration;
import org.apache.shardingsphere.shardingjdbc.orchestration.api.OrchestrationShardingDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import static com.crazymaker.springcloud.message.core.LocalRegistryCenter.TYPE_NAME;

/**
 * 自定义数据源
 */

@Component
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
@AutoConfigureAfter({DataSourceProperties.class})
@Slf4j
public class ShardingRuleConfig {

    private static final String DS_PREFIX = "ds";
    private static final String MSG_LOGIC_TB = "message";
    private static final String MSG_STREAM_LOGIC_TB = "msg_stream";
    // 默认的库数量和表的数量
    // 前面是库的数量
    // 后面是表的数量
    public static final String DEFAULT_SHARDING_RULE_CONFIG = "2;2";
    private volatile AtomicInteger dataSourceCount = new AtomicInteger(0);
    private volatile AtomicInteger tableCount = new AtomicInteger(0);


    @Autowired
    private DataSourceProperties dataSourceProperties;
    private DynamicDataSourceDecorator dynamicDataSourceDecorator;
    private String config = null;


    @Bean
    public DataSource dataSource() throws SQLException {

        DataSource shardingDataSource = buildShardingDataSource();
        dynamicDataSourceDecorator = new DynamicDataSourceDecorator(shardingDataSource);


        return dynamicDataSourceDecorator;
    }

    /**
     * shardingjdbc数据源
     */
    public synchronized DataSource buildShardingDataSource() throws SQLException {
        // 配置真实数据源
        Map<String, DataSource> dataSourceMap = new HashMap<>();


        //多数据源配置
        int dbCount = getDsCounts();

        for (int i = 0; i < dbCount; i++) {
            //数据源 i
            DruidDataSource dataSource0 = buildDruidDataSource(i);
            //ds0,ds1,....
            dataSourceMap.put(DS_PREFIX + i, dataSource0);
        }

        // 配置分片规则
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        //消息表分片规则
        TableRuleConfiguration messageRuleConfig = messageRuleConfig();
        shardingRuleConfig.getTableRuleConfigs().add(messageRuleConfig);
        //继续用add添加分片规则
        //消息流水表分片规则
        TableRuleConfiguration msgSteamRuleConfig = msgSteamRuleConfig();
        shardingRuleConfig.getTableRuleConfigs().add(msgSteamRuleConfig);

        //配置绑定表规则列表,级联绑定表代表一组表，这组表的逻辑表与实际表之间的映射关系是相同的
        shardingRuleConfig.getBindingTableGroups().add(MSG_LOGIC_TB);
        shardingRuleConfig.getBindingTableGroups().add(MSG_STREAM_LOGIC_TB);
        // 多数据源一定要指定默认数据源
        // 只有一个数据源就不需要
        shardingRuleConfig.setDefaultDataSourceName(DS_PREFIX + 0);

        //广播表配置如下;
        shardingRuleConfig.getBroadcastTables().add("user_info");


        Properties p = new Properties();
        //打印sql语句，生产环境关闭
        p.setProperty("sql.show", Boolean.TRUE.toString());
        RegistryCenterConfiguration registryCenter = new RegistryCenterConfiguration(TYPE_NAME);
        OrchestrationConfiguration orchestrationConfig =
                new OrchestrationConfiguration("orchestration-sharding-data-source",
                        registryCenter, false);

        DataSource shardingDataSource = OrchestrationShardingDataSourceFactory.createDataSource(dataSourceMap,
                shardingRuleConfig, p, orchestrationConfig);


     /*
     // 获取已有的配置
        String logic_db_rules = LocalRegistryCenter.values
                .get("/orchestration-sharding-data-source/config/schema/logic_db/rule");
        System.out.println("logic_db_rules = " + logic_db_rules);

         // 获取已有的配置
        String logic_db_datasources = LocalRegistryCenter.values
                .get("/orchestration-sharding-data-source/config/schema/logic_db/datasource");
        System.out.println("logic_db_datasources = " + logic_db_datasources);
*/
        return shardingDataSource;
    }

    /**
     * 获取数据源的数量
     *
     * @return 数据源的数量
     */
    private int getDsCounts() {

        //如果已经有数据
        if (dataSourceCount.get() != 0) {
            return dataSourceCount.get();
        }

        //如果 shardingRuleConfig 变量为空， 则默认为2个 数据源
        if (null == config) {
            config = dataSourceProperties.getRuleConfig();
        }
        if (null == config) {
            config = DEFAULT_SHARDING_RULE_CONFIG;
        }
        String[] array = config.split(";");

        int newVal = Integer.parseInt(array[0]);

        dataSourceCount.set(newVal);

        newVal = Integer.parseInt(array[1]);

        tableCount.set(newVal);

        return dataSourceCount.get();
    }

    /**
     * 获取表的数量
     *
     * @return 数据源的数量
     */
    private int getTableCounts() {

        if (tableCount.get() != 0) {
            return tableCount.get();
        }


        //如果 shardingRuleConfig 变量为空， 则默认为2个 数据源
        if (null == config) {
            config = dataSourceProperties.getRuleConfig();
        }
        if (null == config) {
            config = DEFAULT_SHARDING_RULE_CONFIG;
        }
        String[] array = config.split(";");

        int newVal = Integer.parseInt(array[0]);

        dataSourceCount.set(newVal);

        newVal = Integer.parseInt(array[1]);

        tableCount.set(newVal);

        return tableCount.get();
    }

    /**
     * 消息表的分片规则
     */
    private TableRuleConfiguration messageRuleConfig() {
        String logicTable = MSG_LOGIC_TB;

        //获取实际的 ActualDataNodes
        String actualDataNodes = getActualDataNodes(logicTable);

        TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration(logicTable, actualDataNodes);

        //设置分表策略

        TableShardingAlgorithm tableShardingAlgorithm = new TableShardingAlgorithm(getTableCounts(), getDsCounts());
        ShardingStrategyConfiguration tableShardingStrategy = new StandardShardingStrategyConfiguration("msg_id", tableShardingAlgorithm);
        tableRuleConfig.setTableShardingStrategyConfig(tableShardingStrategy);


        // 配置分库策略（Groovy表达式配置db规则）
//        ShardingStrategyConfiguration dsShardingStrategy = new InlineShardingStrategyConfiguration("msg_id", "ds${msg_id % 2}");
        DsShardingAlgorithm dsShardingAlgorithm = new DsShardingAlgorithm(DS_PREFIX, getDsCounts());
        ShardingStrategyConfiguration dsShardingStrategy = new StandardShardingStrategyConfiguration("msg_id", dsShardingAlgorithm);
        tableRuleConfig.setDatabaseShardingStrategyConfig(dsShardingStrategy);
//        tableRuleConfig.setKeyGeneratorConfig();
        return tableRuleConfig;
    }

    /**
     * 消息stream表的分片规则
     */
    private TableRuleConfiguration msgSteamRuleConfig() {
        String logicTable = MSG_STREAM_LOGIC_TB;

        //获取实际的 ActualDataNodes
        String actualDataNodes = getActualDataNodes(logicTable);

        TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration(logicTable, actualDataNodes);

        //设置分表策略

        TableShardingAlgorithm tableShardingAlgorithm = new TableShardingAlgorithm(getTableCounts(), getDsCounts());
        ShardingStrategyConfiguration tableShardingStrategy = new StandardShardingStrategyConfiguration("msg_id", tableShardingAlgorithm);
        tableRuleConfig.setTableShardingStrategyConfig(tableShardingStrategy);


        // 配置分库策略（Groovy表达式配置db规则）
//        ShardingStrategyConfiguration dsShardingStrategy = new InlineShardingStrategyConfiguration("msg_id", "ds${msg_id % 2}");
        DsShardingAlgorithm dsShardingAlgorithm = new DsShardingAlgorithm(DS_PREFIX, getDsCounts());
        ShardingStrategyConfiguration dsShardingStrategy = new StandardShardingStrategyConfiguration("msg_id", dsShardingAlgorithm);
        tableRuleConfig.setDatabaseShardingStrategyConfig(dsShardingStrategy);
        return tableRuleConfig;
    }


    /**
     * 根据logicTable 获取初始化 actualDataNodes
     */
    public String getActualDataNodes(String logicTable) {


        //多数据源配置
        int dbCount = getDsCounts() - 1;
        //分表配置
        int tableCount = getTableCounts() - 1;

        StringBuilder sb = new StringBuilder();
        sb.append(DS_PREFIX).append("${0..");
        sb.append(dbCount).append("}.");
        sb.append(logicTable).append("${0..");
        sb.append(tableCount).append("}");
        return sb.toString();
    }

    /**
     * 构造 druid数据库链接
     */
    private DruidDataSource buildDruidDataSource(int index) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(dataSourceProperties.getDriverClassName());


        DataSourceProperties.MultiConn connProperties = dataSourceProperties.getMultiConn().get(index);

        dataSource.setUrl(connProperties.getUrl());
        dataSource.setUsername(connProperties.getUsername());
        dataSource.setPassword(connProperties.getPassword());

        DataSourceProperties.DruidProperties druidProperties = dataSourceProperties.getDruid();
        dataSource.setInitialSize(druidProperties.getInitialSize());
        dataSource.setMinIdle(druidProperties.getMinIdle());
        dataSource.setMaxActive(druidProperties.getMaxActive());
        dataSource.setMaxWait(druidProperties.getMaxWait());
        dataSource.setTimeBetweenEvictionRunsMillis(druidProperties.getTimeBetweenEvictionRunsMillis());
        dataSource.setMinEvictableIdleTimeMillis(druidProperties.getMinEvictableIdleTimeMillis());
        String validationQuery = druidProperties.getValidationQuery();
        if (validationQuery != null && !"".equals(validationQuery)) {
            dataSource.setValidationQuery(validationQuery);
        }
        dataSource.setTestWhileIdle(druidProperties.isTestWhileIdle());
        dataSource.setTestOnBorrow(druidProperties.isTestOnBorrow());
        dataSource.setTestOnReturn(druidProperties.isTestOnReturn());
        if (druidProperties.isPoolPreparedStatements()) {
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(druidProperties.getMaxPoolPreparedStatementPerConnectionSize());
        }
        String connectionPropertiesStr = druidProperties.getConnectionProperties();
        if (connectionPropertiesStr != null && !"".equals(connectionPropertiesStr)) {
            Properties connectProperties = new Properties();
            String[] druidPropertiesList = connectionPropertiesStr.split(";");
            for (String propertiesTmp : druidPropertiesList) {
                String[] obj = propertiesTmp.split("=");
                String key = obj[0];
                String value = obj[1];
                connectProperties.put(key, value);
            }
            dataSource.setConnectProperties(connectProperties);
        }
        dataSource.setUseGlobalDataSourceStat(druidProperties.isUseGlobalDataSourceStat());
        WallConfig wallConfig = new WallConfig();
        wallConfig.setMultiStatementAllow(true);
        WallFilter wallFilter = new WallFilter();
        wallFilter.setConfig(wallConfig);
        //打开日志记录过滤器，可通过log4j2,记录sql   application.yml中配置【logging:config: classpath:logConfig/log4j2.xml】
        Slf4jLogFilter slf4jLogFilter = new Slf4jLogFilter();
        slf4jLogFilter.setStatementCreateAfterLogEnabled(false);
        slf4jLogFilter.setStatementCloseAfterLogEnabled(false);
        slf4jLogFilter.setResultSetOpenAfterLogEnabled(false);
        slf4jLogFilter.setResultSetCloseAfterLogEnabled(false);
        List<Filter> filters = new ArrayList<>();
        filters.add(wallFilter);
        filters.add(new StatFilter());
        filters.add(slf4jLogFilter);

        dataSource.setProxyFilters(filters);
        return dataSource;
    }

    public String getConfig() {
        return config;
    }

    //修改配置，动态切换数据源
    public void changeConfig(String newConfig) {


        String[] array = newConfig.split(";");
        if (array.length != 2) {
            log.error("配置有误");
            return;
        }
        int data = Integer.parseInt(array[0]);


        dataSourceCount.set(data);

        data = Integer.parseInt(array[1]);

        tableCount.set(data);

        DataSource shardingDataSource = null;
        try {
            shardingDataSource = buildShardingDataSource();

            dynamicDataSourceDecorator.changShardingDataSource(shardingDataSource);


        } catch (SQLException e) {
            e.printStackTrace();
        }

        config = newConfig;
    }


    public boolean checkConfig(String newConfig) {
        try {
            String[] array = newConfig.split(";");
            if (array.length != 2) {
                log.error("配置有误");
                return false;
            }
            int data = Integer.parseInt(array[0]);


            dataSourceCount.set(data);

            data = Integer.parseInt(array[1]);

            tableCount.set(data);

            log.info("new data is ok {}", newConfig);
        } catch (Throwable e) {
            return false;
        }
        return true;
    }


}
